<?php

/**
 * SeoParser - parser for seofeatures. It makes needed updatings and synchronization
 *
 * @author Eugene I. Nezhuta
 */
class RCMS_Object_Parser_SeoParser {

	/**
	 * Name of prsculpting help file
	 *
	 */
	const PFS_FILE_NAME = 'pfs.prs';

	const SITEMAP_FILE_NAME = 'sitemap.xml';

	/**
	 * SeoParser Model
	 *
	 * @var RCMS_Object_Parser_SeoParserModel
	 */
	private $_model = null;

	/**
	 * Content to parse
	 *
	 * @var string
	 */
	private $_seoContent = '';

	/**
	 * Current domain name
	 *
	 * @var string
	 */
	private $_currentDomain = '';

	/**
	 * Generator object
	 *
	 * @var  RCMS_Object_Generator_Generator
	 */
	private $_generator = null;


	/**
	 * Website url
	 *
	 * @var string
	 */
	public $_websiteUrl;

	/**
	 * Array of tags extracted from the content.
	 *
	 * @var array
	 */
	private $_tagsReplacement = array();


	public function  __construct($content = '') {
		if(!empty($content)) {
			$this->_seoContent = $content;
		}
		$this->_model = new RCMS_Object_Parser_SeoParserModel();
		$this->_generator = new RCMS_Object_Generator_Generator();
		$this->_currentDomain = 'http://' . $_SERVER['SERVER_NAME'] . '/';
		$this->_websiteUrl = unserialize(Zend_Registry::get('config'))->website->website->url;
	}

	/**
	 * Set content for parser
	 *
	 * @param string $content
	 */
	public function setContentForParsing($content) {
		$this->_seoContent = $content;
	}

	/**
	 * Updation of link references for container
	 *
	 * @param integer $containerId
	 */
	public function updateLinkReference($containerId) {
		$links = preg_match_all('~<a.*href\s*="(.*)".*>.*</a>~U', $this->_seoContent, $matches);
		$sitePagesUrls = $this->_model->getSitePagesUrls();
		if(!empty($matches[0])) {
			$hrefs = $matches[1];
			foreach ($sitePagesUrls as $sitePagesUrl) {
				foreach ($hrefs as $href) {
					$href = str_replace('.html', '', $href);
					if(($sitePagesUrl == $href) || ($this->_currentDomain . $sitePagesUrl == $href)) {
						$href = (stristr($href, $this->_currentDomain)) ? $href : $this->_currentDomain . $href;
						if(!$this->_checkReferenseLinkExists($containerId, $href)) {
							$this->_model->insertLinkReference($containerId, $href);
						}
					}
				}
			}
		}
	}

	/**
	 * Check if link reference for container already exists
	 *
	 * @param integer $cId
	 * @param string $href
	 * @return boolean
	 */
	private function _checkReferenseLinkExists($cId, $href) {
		$result = $this->_model->checkExists($cId, $href);
		if($result) {
			return true;
		}
		return false;
	}

	/**
	 * Create sitemap xml file
	 *
	 */
	public function createSiteMapFile() {
		$draftCategoryId = $this->_model->selectDraftCategoryId();
		$timestamp = time();
		$siteMapFilePages = array();
		$pages = $this->_model->selectAllPages();
		if(is_array($pages)) {
			foreach ($pages as $page) {

				if($page['url'] == RCMS_Object_QuickConfig_QuickConfig::$draftCategoryUrl || $page['parentCategoryId'] == $draftCategoryId) {
					continue;
				}
				switch($page['url']) {
					case 'index': $priority = '1.0'; break;
					default: $priority = '0.8'; break;
				}

				$siteMapFilePages[] = array(
					'location' => trim($this->_websiteUrl.$page['url']) . '.html',
					'lastmod' => date('c', $timestamp),
					'changefreq' => 'daily',
					'priority' => $priority
				);
			}

			$siteMapContent = $this->_generator->generateSiteMapXml($siteMapFilePages);
		}
	}

	/**
	 * Chain synchronization on page update or add
	 *
	 * @param string $oldUrl old page url
	 * @param string $newUrl new page url
	 * @param string $pageH1Tag h1 tag of the page
	 * @param integer $pageId id of the page
	 */
	public function chainSyncOnUpdate($oldUrl, $newUrl, $pageH1Tag, $pageId) {
	//update all links in content
		$this->_synchronizeLinks($oldUrl, $newUrl, $pageH1Tag);
		$this->_synchronizeDeeplinks($this->_currentDomain . $oldUrl .'.html', $this->_currentDomain . $newUrl . '.html');
		if($oldUrl != $newUrl) {
		//put renamed page into db for 301 redirect
			$this->_updateRenamed($pageId, $oldUrl, $newUrl);
			//update redirects
			$this->_synchronizeRedirects();
		}
		//synchronize page runk sculpting file
		$this->_syncPRSFile();
	}

	/**
	 * Synchronize links on page updating
	 *
	 * @param string $old
	 * @param string $new
	 * @param string $pageH1
	 */
	private function _synchronizeLinks($old, $new, $pageH1) {
		if($old == $new) {
			return;
		}
		$containersIdsToSynchronize = $this->_model->getContainersIdsForSync($old, $this->_currentDomain);
		if(!empty($containersIdsToSynchronize)) {
			foreach ($containersIdsToSynchronize as $containerId) {
				$container     = new RCMS_Object_Container_Container($containerId);
				$content       = $container->getContent();
				$fullOldUrl    = $this->_websiteUrl . $old . '.html';
				$fullNewUrl    = $this->_websiteUrl . $new . '.html';
				$shortOldUrl   = $old . '.html';
				$urlPattern    = '~(<a\s*[^\s]*\s*href=")(' . $fullOldUrl . '|' . $shortOldUrl . ')("\s*[^\s]*>.*</a>)~sU';
				$titlePatterns = array(
					'~(<a\s+title=")(.*)("\s+href="' . $fullNewUrl . '"\s*[^\s]*>.*</a>)~sU',
					'~(<a\s*[^\s]*\s*href="' . $fullNewUrl . '"\s+title=")(.*)("\s*[^\s]*>.*</a>)~sU'
				);
				$content = preg_replace($urlPattern, '${1}' . $fullNewUrl . '${3}', $content);
				$content = preg_replace($titlePatterns, '${1}' . $pageH1 . '${3}', $content);
				$container->setContent($content);
				$container->save();
				unset($container);
				$this->_model->updateLinkReference($containerId, $old, $new, $this->_currentDomain);
				unset($container);
			}
		}
	}

	/**
	 * Synchronize deeplinks
	 *
	 * @param string $oldUrl
	 * @param string $newUrl
	 */
	private function _synchronizeDeeplinks($oldUrl, $newUrl) {

		$this->_model->replaceDeeplinksUrl($oldUrl, $newUrl);
	}

	/**
	 * Update of renamed pages for 301 redirect
	 *
	 * @param integer $pageId
	 * @param string $url
	 * @param string $newUrl
	 */
	private function _updateRenamed($pageId, $url, $newUrl) {
		if($pageId && $url && $newUrl) {
			$this->_cleanBadRedirect($newUrl);
			$this->_model->insertRenamedPage($pageId, urlencode(str_replace('?', '', urldecode($url))), $newUrl);
		}
	}

	/**
	 * Synchronize 301 redirects
	 *
	 * @param integer $pageId
	 */
	private function _synchronizeRedirects() {
		$redirectTable = $this->_model->getRenamedPages();
		if(!empty($redirectTable)) {
			$i = 0;
			foreach ($redirectTable as $page => $data) {
				$preparedRedirectTable[$i]['currUrl'] = $data['currUrl'];
				$preparedRedirectTable[$i]['oldUrl'] = str_replace('\-', '-', preg_quote(urldecode($data['oldUrl'])));
				$i++;
			}
		}
		if(empty($preparedRedirectTable)) {
			$preparedRedirectTable = '';
		}
		$this->_generator->generateRedirects($preparedRedirectTable);
	}

	/**
	 * Refresh the .htaccess file
	 *
	 */
	public function refreshHtAccess() {
		$this->_synchronizeRedirects();
	}

	public function refreshRobotsTxt($sitemapUrl = '') {
		if(!$sitemapUrl) {
			$sitemapUrl = $this->_websiteUrl . self::SITEMAP_FILE_NAME;
		}
		return $this->_generator->generateRobotsTxt($sitemapUrl);
	}


	public function applyDeeplinksPerPage($containerId = 0) {
		$pages = array();
		if($containerId) {
			$pageId = $this->_model->selectPageIdByContainerId($containerId);
			$contents = $this->_model->selectContentsByPageId($pageId);
			$this->applyDeeplinks($contents);
		}
		else {
			$pages = $this->_model->selectAllPages();
			if(!empty ($pages)) {
				foreach ($pages as $pageData) {
					$contents = $this->_model->selectContentsByPageId($pageData['pageId']);
					$this->applyDeeplinks($contents);
				}
			}
		}
	}



	/**
	 * Replace deeplinks in contents
	 *
	 */
	//public function applyDeeplinks($containerId = 0) {

	public function applyDeeplinks($contents) {
		$deeplinks = $this->_bobbleSortDeeplinks($this->_model->selectAllDeeplinks());
		if(!$deeplinks || empty($deeplinks)) {
			return false;
		}
		//$contents        = $this->_model->selectContents($containerId);
		$deeplincContent = array();
		$dataForUpdate   = array();
		$h1s             = $this->_getH1s($contents);
		foreach ($contents as $key => $contentData) {
			if(!isset($contentData['content_value'])) {
				continue;
			}
			$contents[$key]['content_value'] = $this->_extractTags($contentData['content_value'], $contentData['page_name']);
			$contents[$key]['len'] = strlen($contents[$key]['content_value']);
			foreach ($deeplinks as $dpkey => $deeplinkData) {
				$tr = $this->_tagsReplacement;
				$extracted = array_map(array($this, '_deeplinkCallback'), $this->_tagsReplacement);
				if($applied || in_array($deeplinkData['name'], $extracted)) {
					unset ($deeplinks[$dpkey]);
					$applied = false;
					//break;
					continue;
				}
				//$deeplinkData['name'] = strtolower($deeplinkData['name']);
				if(($deeplinkData['name'] == strtolower($contentData['page_name'])) || (strtolower($deeplinkData['name'] == strtolower($contentData['page_name']))) || !trim($contentData['content_value']) || ($this->_websiteUrl . $contentData['page_url'] . '.html' == $deeplinkData['url'])) {
					continue;
				}
				switch ($deeplinkData['type']) {
					case 'int':
						if($h1 = array_search($deeplinkData['url'], $h1s)) {
							$url = '<a href="' . $deeplinkData['url'] . '" title="' . preg_replace('/[^A-Za-z0-9\s]+/', '', $h1) . '"' . ($deeplinkData['nofollow'] == 1 ? ' rel="nofollow"' : '') . '>' . $deeplinkData['name'] . '</a>';
						}
						else {
							$url = '<a href="' . $deeplinkData['url'] . '" title=""' . ($deeplinkData['nofollow'] == 1 ? ' rel="nofollow"' : '') . '>' . $deeplinkData['name'] . '</a>';
						}
					break;
					case 'ext':
						$url = '<a href="' . $deeplinkData['url'] . '" target="_blank">' . $deeplinkData['name'] . '</a>';
					break;
				}
				$contents[$key]['content_value'] = preg_replace('~([\>]{1}|\s+|[\/\>]{1})(' . preg_quote($deeplinkData['name']) . ')([\<]{1}|\s+|[.,!\?]+)~sUu', '$1' . $url . '$3', $contents[$key]['content_value'], 1);

				$appl = $contents[$key]['len'] != strlen($contents[$key]['content_value']);

				$contents[$key]['content_value'] = $this->_extractTags($contents[$key]['content_value'], $contentData['page_name']);
				if($appl) {
					$applied           = true;
					$deeplincContent[] = $deeplinkData['id'];
					$dataForUpdate[$contentData['content_id']] = array(
						'containerId' => $contentData['container_id'],
						'content'     => $contents[$key]['content_value']
					);
					$dataForUpdate[$contentData['content_id']]['deeplinksIds'] = $deeplincContent;
				}
			}
		}
		if(!empty($dataForUpdate)) {
			foreach($dataForUpdate as $contentId => $data) {
				$data['content'] = $this->_putTagsBack($data['content']);
				$this->_model->updateContent($contentId, $data['content']);
				if(isset($data['deeplinksIds']) && is_array($data['deeplinksIds'])) {
					foreach($data['deeplinksIds'] as $deeplinkId) {
						$this->_model->insertIdDeeplinkAndContent($deeplinkId, $contentId);
					}
				}
				$this->_seoContent = $data['content'];
				$this->updateLinkReference($data['containerId']);
			}
		}
	}

	protected function _deeplinkCallback($item) {
		return strip_tags($item);
	}

	private function _getH1s($contents) {
		$h1s = array();
		foreach($contents as $key => $contentData) {
			$h1s[$contentData['page_h1']] = $this->_websiteUrl . $contentData['page_url'] . RCMS_Object_QuickConfig_QuickConfig::$regPageExtension;
		}
		return $h1s;
	}

	private function _bobbleSortDeeplinks($deeplinks) {
		$arraySize = count($deeplinks) - 1;
		for($i = $arraySize; $i >= 0; $i--) {
			for($j = 0; $j <= ($i-1); $j++) {
				$deeplinks[$j]['len'] = strlen($deeplinks[$j]['name']);
				$deeplinks[$j+1]['len'] = strlen($deeplinks[$j+1]['name']);
				if($deeplinks[$j]['len'] < $deeplinks[$j+1]['len']) {
					$tmp = $deeplinks[$j];
					$deeplinks[$j] = $deeplinks[$j+1];
					$deeplinks[$j+1] = $tmp;
				}
			}
		}
		return $deeplinks;
	}

	private function _extractTags($content, $pageName) {
		//$pattern = '~<a\s+(title=".*")*\s*href="' . $data[$key]['url_deeplink'] . '"\s+(title=".*")*>' . $data[$key]['name_deeplink'] . '</a>~U';
		preg_match_all('~<h[1-9]{1}.*>.*</h[1-9]{1}>~suU', $content, $matchesHeaders);
		preg_match_all('~<a.*>.*</a>~suU', $content, $matchesLinks);
		preg_match_all('~{\$.*}~sUu', $content, $matchesWidgets);

		if(!empty ($matchesHeaders)) {
			$content = $this->_processTagsMatches($matchesHeaders, $content, RCMS_Object_QuickConfig_QuickConfig::$deeplinkReplacementHeaderPattern);
		}
		if(!empty ($matchesLinks)) {
			$content = $this->_processTagsMatches($matchesLinks, $content, RCMS_Object_QuickConfig_QuickConfig::$deeplinkReplacementLinkPattern);
		}
		if(!empty($matchesWidgets)) {
			$content = $this->_processTagsMatches($matchesWidgets, $content, RCMS_Object_QuickConfig_QuickConfig::$deeplinkReplacementHeaderPattern);
		}
		return $content;
	}

	private function _processTagsMatches($matches, $content, $pattern) {
		foreach ($matches as $value) {
			if(!is_array($value)) {
				break;
			}
			foreach ($value as $key => $item) {
				if(preg_match('~'. preg_quote($item) .'~Ui', $content)) {
					$sub = $pattern . microtime();
					$this->_tagsReplacement[$sub] = $item;
					$content = str_replace($item, $sub, $content);
				}
			}
		}
		return $content;
	}

	private function _putTagsBack($content) {
		foreach ($this->_tagsReplacement as $sub => $value) {
			$content = str_replace($sub, $value, $content);
		}
		return $content;
	}

	/**
	 * Delete deeplink from container content
	 *
	 * @param integer $id
	 */
	public function deleteDeeplinkFromContent($id) {
		$data = $this->_model->selectDeeplinkAndContentById($id);
		if(!empty($data)) {
			foreach ($data as $key => $value) {
				$pattern = '~<a\s+(title=".*")*\s*href="' . $data[$key]['url_deeplink'] . '"\s+(title=".*")*>' . $data[$key]['name_deeplink'] . '</a>~U';
				$data[$key]['content'] = preg_replace($pattern, $data[$key]['name_deeplink'], $data[$key]['content']);
				$this->_model->updateContent($data[$key]['content_id'], $data[$key]['content']);
			}
			$this->_model->deleteDeeplinkAndContentByDeeplinkId($id);
		}
	}

	/**
	 * Clean redirect table. Prevent that wether when browser gets into loop
	 *
	 * @param string $url
	 * @return void
	 */
	private function _cleanBadRedirect($url) {
		$badRedirectRecordId = $this->_model->selectBadRedirectId($url);
		if(empty ($badRedirectRecordId) || !$badRedirectRecordId) {
			return;
		}
		$this->_model->cleanBadRedirect($badRedirectRecordId, preg_match('\.html$', $url) ? $url : $url . '.html');
	}

	/**
	 * Interface method for cleaning bad redirects
	 *
	 * @param string $url
	 */
	public function cleanBadRedirect($url) {
		$this->_cleanBadRedirect($url);
	}

	/**
	 * Synchronize news routes according to news folder
	 *
	 */
	public function syncNewsRoutes() {
		$this->_generator->generateNewsRoutes();
	}

	/**
	 * Run page rank sculpting
	 *
	 * @param integer $siloId
	 * @return string
	 */
	public function runPRSculpting($siloId) {
		$silo = new RCMS_Object_Silo_Silo($siloId);
		$siloPages = $silo->getRelatedPages();
		if(preg_match_all('/<a.*href="([\w0-9-\s\p{L}\p{M}\p{P}]+\.html)".*>/u', $this->_seoContent, $matches)) {
			$hrefs = array_unique($matches[1]);
			foreach ($hrefs as $href) {
				if(in_array(str_replace(array($this->_websiteUrl, '.html'), '', $href), $siloPages)) {
					$keys = array_keys($hrefs, $href);
					unset($hrefs[$keys[0]]);
				}
			}
			if(in_array($this->_websiteUrl . 'index.html', $hrefs)) {
				$keys = array_keys($hrefs, $this->_websiteUrl . 'index.html');
				unset($hrefs[$keys[0]]);
			}
			if(in_array($this->_websiteUrl . 'index', $hrefs)) {
				$keys = array_keys($hrefs, $this->_websiteUrl . 'index');
				unset($hrefs[$keys[0]]);
			}
			if(in_array('index.html', $hrefs)) {
				$keys = array_keys($hrefs, 'index.html');
				unset($hrefs[$keys[0]]);
			}
			foreach ($hrefs as $key => $replHref) {
				$pageId = $this->_model->selectPageIdByUrl(urlencode(str_replace(array($this->_websiteUrl, '.html'), '', $replHref)));
				$this->_seoContent = preg_replace('~<a.*href="('. $replHref .')".*>(.*)</a>~Uu', '<span id="' . $pageId .'" class="nsp">$2</span>', $this->_seoContent);
			}
		}
		return $this->_seoContent;
	}

	/**
	 * Sinchronize page rank sculpting file (interface method)
	 *
	 */
	public function syncPRSFile() {
		$this->_syncPRSFile();
	}

	/**
	 * Sinchronize page rank sculpting file
	 *
	 */
	protected function _syncPRSFile() {
		$config = unserialize(Zend_Registry::get('config'));
		$path = $config->website->website->path . 'tmp/';
		$this->_generator->generatePRSFile($path, $this->getPagesUrlsAndIds());
	}

	/**
	 * Get pages urls and ids
	 *
	 * @return array
	 */
	public function getPagesUrlsAndIds() {
		$pagesAndIds = array();
		$result = $this->_model->getPagesUrlsAndIds();
		foreach ($result as $item) {
			$item['url'] = urldecode($item['url']);
			$pagesAndIds[] = $item;
		}
		return $pagesAndIds;
	}

	public function makeCanonicRules() {
		$hasWww = false;
		//prepare website url
		$url = parse_url($this->_websiteUrl);
		$url['path'] = (isset($url['path']) && $url['path'] != '/') ? $url['path'] : '';
		if ((strpos($this->_websiteUrl, 'http://www.') !== false) || (strpos($this->_websiteUrl, 'https://www.') !== false)) {
			$hasWww = true;
			$withoutWww = preg_quote(str_replace('www.', '', $url['host']));
		}
		else {
			$withoutWww = preg_quote($url['host']);
		}
		//checking if site in subfolder, then return
		if(strlen($url['path']) > 1) {
			return;
		}
		elseif(strlen($url['path']) == 1 && $url['path'] != '/') {
			return;
		}
		//define pattern and replacement
		$pattern = '/\#\{\%canonic_rules\%\}/';
		if($hasWww) {
			$replacement = "RewriteCond %{HTTP_HOST} !^www\." . $withoutWww . "$\nRewriteRule ^(.*)$ " . $url['scheme'] . "://www." . stripslashes($withoutWww) . "/$1 [R=permanent,L]";
		}
		else {
			$replacement = "RewriteCond %{HTTP_HOST} !^" . $withoutWww . "$\nRewriteRule ^(.*)$ " . $url['scheme'] . "://" . stripslashes($withoutWww) . "/$1 [R=permanent,L]";
		}
		$config = unserialize(Zend_Registry::get('config'));
		//geting main .htaccess
		$mainHtaccessPath = $config->website->website->path . '.htaccess';
		$mainHtaccess = RCMS_Tools_FilesystemTools::getFile($mainHtaccessPath);
		//var_dump($mainHtaccess); die();
		if(!empty ($mainHtaccess)) {
			if(preg_match($pattern, $mainHtaccess)) {
				$mainHtaccess = preg_replace($pattern, $replacement, $mainHtaccess);
				if(is_writable($mainHtaccessPath)) {
					RCMS_Tools_FilesystemTools::saveFile($mainHtaccessPath, $mainHtaccess);
				}
			}
		}
	}
}
